﻿//OrderLogMarketDepthBuilder.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com

namespace StockSharp.Algo
{
	using System;
	using System.Collections.Generic;

	using Ecng.Common;

	using StockSharp.BusinessEntities;

	/// <summary>
	/// Построитель стакана из лога заявок.
	/// </summary>
	public class OrderLogMarketDepthBuilder
	{
		private DateTime? _lastUpdateTime;

		// TODO надо сделать это задаваемым
		private readonly TimeSpan _clearingBeginTime = new TimeSpan(18, 45, 01);

		//private readonly Dictionary<long, OrderLogItem> _trades = new Dictionary<long, OrderLogItem>();
		private OrderLogItem _matchingOrder;

		/// <summary>
		/// Создать <see cref="OrderLogMarketDepthBuilder"/>.
		/// </summary>
		/// <param name="depth">Стакан.</param>
		public OrderLogMarketDepthBuilder(MarketDepth depth)
		{
			if (depth == null)
				throw new ArgumentNullException("depth");

			_depth = depth;
		}

		private readonly MarketDepth _depth;

		/// <summary>
		/// Стакан.
		/// </summary>
		public MarketDepth Depth
		{
			get { return _depth; }
		}

		///// <summary>
		///// Событие появления новых сделок.
		///// </summary>
		//public event Action<IEnumerable<Trade>> NewTrades;

		/// <summary>
		/// Добавить новую строчку из ордер-лога к стакану.
		/// </summary>
		/// <param name="item">Строчка ордер-лога.</param>
		/// <returns>Был ли изменен стакан.</returns>
		public bool Update(OrderLogItem item)
		{
			if (item == null)
				throw new ArgumentNullException("item");

			var order = item.Order;

			var changed = false;

			try
			{
				// Очистить стакан в вечерний клиринг
				if (order.Time.TimeOfDay >= _clearingBeginTime)
				{
					// Garic - переделал
					// Очищаем только в рабочие дни поскольку в субботу/воскресенье допустима отмена заявок
					var exchange = order.Security.ExchangeBoard ?? ExchangeBoard.Forts;

					if (_lastUpdateTime != null && _lastUpdateTime.Value.TimeOfDay < _clearingBeginTime && exchange.WorkingTime.IsTradeDate(order.Time, true))
					{
						_depth.Update(new Quote[0], new Quote[0], true, order.Time);
						_matchingOrder = null;
						changed = true;
					}
				}

				_lastUpdateTime = order.Time;

				if (!order.IsSystem || item.Trade != null || order.Price == 0 /* нулевая цена может появится при поставке опционов */)
					return changed;

				if (item.IsRegistered())
				{
					changed = TryApplyTrades(null);

					if (
							(order.Direction == OrderDirections.Buy && (_depth.BestAsk == null || order.Price < _depth.BestAsk.Price)) ||
							(order.Direction == OrderDirections.Sell && (_depth.BestBid == null || order.Price > _depth.BestBid.Price))
						)
					{
						if (order.ExecutionCondition == OrderExecutionConditions.PutInQueue)
						{
							_depth.AddQuote(new Quote
							{
								Security = order.Security,
								OrderDirection = order.Direction,
								Price = order.Price,
								Volume = order.Volume,
							});

							changed = true;
						}
					}
					else
					{
						_matchingOrder = item;

						// mika
						// из-за того, что могут быть кросс-сделки, матчинг только по заявкам невозможен
						// (сначала идет регистрация вглубь стакана, затем отмена по причине кросс-сделки)
						// http://forum.rts.ru/viewtopic.asp?t=24197
						// 
					}
				}
				else if (item.IsCanceled())
				{
					var isSame = _matchingOrder != null && _matchingOrder.Order.Id == item.Order.Id;

					changed = TryApplyTrades(item);

					if (!isSame && order.ExecutionCondition == OrderExecutionConditions.PutInQueue)
					{
						// http://forum.rts.ru/viewtopic.asp?t=24197
						if (item.GetCancelReason() != OrderLogCancelReasons.CrossTrade)
							_depth.Remove(order.Direction, order.Price, order.Volume);

						changed = true;
					}
				}
				else
				{
					throw new ArgumentException("Неизвестная строчка лога заявок {0}.".Put(item), "item");

					// для одной сделки соответствуют две строчки в ОЛ
					//_trades[item.Trade.Id] = item;
				}
			}
			finally
			{
				if (changed)
					_depth.LastChangeTime = order.Time;
			}

			return changed;
		}

		private bool TryApplyTrades(OrderLogItem item)
		{
			if (_matchingOrder == null)
				return false;

			try
			{
				var order = _matchingOrder.Order;

				var volume = order.Volume;

				if (item != null && _matchingOrder.Order.Id == item.Order.Id)
					volume -= item.Order.Volume;

				// если заявка была вся отменена. например, по причине http://forum.rts.ru/viewtopic.asp?t=24197
				if (volume == 0)
					return false;

				var removingQuotes = new List<Tuple<Quote, decimal>>();

				foreach (var quote in (order.Direction == OrderDirections.Buy ? _depth.Asks : _depth.Bids))
				{
					if ((order.Direction == OrderDirections.Buy && order.Price < quote.Price) || (order.Direction == OrderDirections.Sell && order.Price > quote.Price))
						break;

					if (volume >= quote.Volume)
					{
						removingQuotes.Add(new Tuple<Quote, decimal>(quote, quote.Volume));

						volume -= quote.Volume;

						if (volume == 0)
							break;
					}
					else
					{
						removingQuotes.Add(new Tuple<Quote, decimal>(quote, volume));

						volume = 0;
						break;
					}
				}

				// в текущей момент Плаза не транслирует признак MatchOrCancel через ОЛ, поэтому сделано на будущее
				if (order.ExecutionCondition == OrderExecutionConditions.MatchOrCancel && volume > 0)
					return false;

				//NewTrades.SafeInvoke(removingQuotes.Select(t => new Trade
				//{
				//    Security = Depth.Security,
				//    Time = order.Time,
				//    Price = t.Item1.Price,
				//    Volume = t.Item2,
				//}));

				foreach (var removingQuote in removingQuotes)
					_depth.Remove(order.Direction.Invert(), removingQuote.Item1.Price, removingQuote.Item2);

				if (volume > 0)
				{
					if (order.ExecutionCondition == OrderExecutionConditions.PutInQueue)
					{
						_depth.AddQuote(new Quote
						{
							Security = order.Security,
							Volume = volume,
							Price = order.Price,
							OrderDirection = order.Direction,
						});
					}
				}

				return true;
			}
			finally
			{
				_matchingOrder = null;
			}
		}
	}
}